#----------------------------------------------
# -*- encoding=utf-8 -*-                      #
# __author__:'xiaojie'                        #
# CreateTime:                                 #
#        2019/3/25 9:58                       #
#                                             #
#               天下风云出我辈，                 #
#               一入江湖岁月催。                 #
#               皇图霸业谈笑中，                 #
#               不胜人生一场醉。                 #
#----------------------------------------------

# https://github.com/keras-team/keras/blob/master/examples/variational_autoencoder.py
# https://github.com/bojone/vae
# 用keras实现vae

from __future__ import print_function

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

from keras.layers import Input,Dense,Lambda
from keras.models import Model
from keras import backend as K
from keras import metrics
from keras.datasets import mnist
from keras.utils import to_categorical

batch_size = 100
original_dim = 784
latent_dim = 2 # 隐变量取2维只是为了方便后面画图
intermediate_dim = 256
epochs = 50
epsilon_std = 1.0
num_classes = 10

#加载mnist数据集
(x_train,y_train_),(x_test,y_test_) = mnist.load_data()
print(x_train[1,:,:])
x_train= x_train.astype('float32')/255.0
x_test = x_test.astype('float32')/255.0
x_train = x_train.reshape((len(x_train),np.prod(x_train.shape[1:])))
x_test= x_test.reshape((len(x_test),np.prod(x_test.shape[1:])))
y_train = to_categorical(y_train_,num_classes)
y_test = to_categorical(y_test_,num_classes)


x=Input(shape=(original_dim,))
h = Dense(intermediate_dim,activation='relu')(x)

# 算p(z|x)的均值与方差
z_mean = Dense(latent_dim)(h)
z_log_var = Dense(latent_dim)(h)

# 重参数技巧
def sampling(args):
    z_mean,z_log_var = args
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0],latent_dim),
                              mean=0,stddev=epsilon_std) # 从N(0,1)中采样得到epsilon
    print('BBBBBB',epsilon)
    return z_mean+K.exp(z_log_var/2)*epsilon

# 重参数层，相当于给输入加入噪声
z = Lambda(sampling,output_shape=(latent_dim,))([z_mean,z_log_var])

# 解码层,也就是生成器部分
decoder_h = Dense(intermediate_dim,activation='relu')
decoder_mean = Dense(original_dim,activation='sigmoid')
h_decoded = decoder_h(z)
x_decoded_mean = decoder_mean(h_decoded)

# 建立模型
vae = Model(x,x_decoded_mean)

# xent_loss是重构的loss，kl_loss是KL LOSS
xent_loss = original_dim*metrics.binary_crossentropy(x,x_decoded_mean)
print('TTTTTTTTTTTTT',xent_loss.shape)
kl_loss = -0.5*K.sum(1+z_log_var-K.square(z_mean)-K.exp(z_log_var),axis=-1)
vae_loss = K.mean(xent_loss+kl_loss)
print('xent_loss:{},kl_loss:{},vae_loss:{}'.format(xent_loss,kl_loss,vae_loss))


# add_loss是新增的方法，用于更灵活的添加各种loss
vae.add_loss(vae_loss)
vae.compile(optimizer='rmsprop')
vae.metrics_names.append('kl_loss')
vae.metrics_tensors.append(kl_loss)

vae.fit(x_train,
        shuffle=True,
        epochs=epochs,
        batch_size = batch_size,
        validation_data=(x_test,None))

# 构建encoder，然后观察各个数字在隐空间的分布
encoder = Model(x,z_mean)
x_test_encoded = encoder.predict(x_test,batch_size=batch_size)
plt.figure(figsize=(6,6))
plt.scatter(x_test_encoded[:,0],x_test_encoded[:,1],c=y_test_)
plt.colorbar()
plt.show()

# 构建生成器
decoder_input = Input(shape=(latent_dim,))
_h_decoded = decoder_h(decoder_input)
_x_decoded_mean = decoder_mean(_h_decoded)
generator = Model(decoder_input,_x_decoded_mean)

# 观察隐变量的两个维度变化是如何影响输出结果的
n = 15 # figure with 15x15 digits
digit_size = 28
figure = np.zeros((digit_size*n,digit_size*n))

# 用正态分布的分位数来构建隐变量对
grid_x = norm.ppf(np.linspace(0.05,0.95,n))
grid_y = norm.ppf(np.linspace(0.05,0.95,n))

for i,yi in enumerate(grid_x):
    for j,xi in enumerate(grid_y):
        z_sample = np.array([[xi,yi]])
        x_decoded = generator.predict(z_sample)
        digit = x_decoded[0].reshape(digit_size,digit_size)
        figure[i*digit_size:(i+1)*digit_size,j*digit_size:(j+1)*digit_size] = digit

plt.figure(figsize=(10,20))
plt.imshow(figure,cmap='Greys_r')
plt.show()
